package com.hmy.finance.service.core.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.hmy.ccp.common.response.BasePageResponse;
import com.hmy.ccp.error.exception.HMYRuntimeException;
import com.hmy.finance.api.bo.core.request.enums.FeeTypeEnum;
import com.hmy.finance.api.bo.core.request.enums.StatementTypeEnum;
import com.hmy.finance.bo.request.ReceivablesDeviceQueryRequest;
import com.hmy.finance.bo.request.ReceivablesDeviceQueryRequest.ContractDeviceDetailRequest;
import com.hmy.finance.bo.request.ReceivablesSumQueryRequest;
import com.hmy.finance.bo.response.ReceivablesDevicePageQueryResponse;
import com.hmy.finance.bo.response.ReceivablesDeviceSumQueryResponse;
import com.hmy.finance.bo.response.ReceivablesSumQueryResponse;
import com.hmy.finance.common.enums.BusinessBillTypeEnum;
import com.hmy.finance.common.utils.FinanceCalculateUtil;
import com.hmy.finance.dal.dos.ReceivablesSumQueryDo;
import com.hmy.finance.dal.dos.ReceiveAdjustSumQueryDo;
import com.hmy.finance.dal.mapper.DeviceDailyBillMapper;
import com.hmy.finance.dal.mapper.DeviceProfitLossDetailMapper;
import com.hmy.finance.dal.mapper.DeviceRentalRecordMapper;
import com.hmy.finance.dal.mapper.ProfitLossRecordMapper;
import com.hmy.finance.dal.mapper.ReceivableInventoryMapper;
import com.hmy.finance.dal.mapper.ReconciliationDeviceStatementMapper;
import com.hmy.finance.dal.mapper.receiveAdjust.ReceiveAdjustMapper;
import com.hmy.finance.dal.po.*;
import com.hmy.finance.dal.po.json.ProfitLossInfoJson;
import com.hmy.finance.service.core.IRevenueService;
import com.hmy.finance.service.core.dto.ReceivableQueryDto;
import com.hmy.finance.service.core.dto.ReceivablesDeviceQueryDto;
import com.hmy.finance.service.core.dto.ReceivablesDeviceQueryDto.ContractDeviceDetailDto;
import com.hmy.finance.service.statement.IReconciliationStatementProviderService;
import com.hmy.finance.service.statement.dto.CalculateStatementDeviceDto;
import com.hmy.finance.service.statement.dto.CreateStatementDeviceDto;
import com.hmy.finance.service.statement.dto.CreateStatementDto;
import com.hmy.finance.service.statement.dto.ReconciliationStatementDto;
import com.hmy.finance.service.statement.statementHandlerFactory.*;

import java.math.RoundingMode;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author wenjian
 * @description 营收调整
 * @createDate 2024-03-07
 */

@Slf4j
@Service
public class RevenueServiceImpl implements IRevenueService {

    @Resource
    private IReconciliationStatementProviderService iReconciliationStatementProviderService;

    @Resource
    private ReceivableInventoryMapper receivableInventoryMapper;

    @Resource
    private ReceiveAdjustMapper receiveAdjustMapper;

    @Resource
    private DeviceDailyBillMapper deviceDailyBillMapper;

    @Resource
    private DeviceRentalRecordMapper deviceRentalRecordMapper;

    @Resource
    private ProfitLossRecordMapper profitLossRecordMapper;

    @Resource
    private DeviceProfitLossDetailMapper deviceProfitLossDetailMapper;

    @Resource
    private ReconciliationDeviceStatementMapper reconciliationDeviceStatementMapper;

    @Resource
    private HistoryStatementHandler historyStatementHandler;

    @Resource
    private MonthStatementHandler monthStatementHandler;

    @Resource
    private PeriodStatementHandler periodStatementHandler;

    @Resource
    private FlexibleStatementHandler flexibleStatementHandler;

    @Override
    public ReceivablesSumQueryResponse queryReceivablesSum(ReceivablesSumQueryRequest request) {
        ReceivableQueryDto receivableQueryDto = ReceivableQueryDto.convertToDto(request);
        //营收金额相关字段
        ReceivablesSumQueryDo receivablesDo = receivableInventoryMapper.statReceivablesAmount(receivableQueryDto);
        //调整金额
        ReceiveAdjustSumQueryDo adjustDo = receiveAdjustMapper.statAdjustAmount(receivableQueryDto);

        return ReceivablesSumQueryResponse.convertToRsp(receivablesDo, adjustDo);
    }

    @Override
    public ReceivablesDeviceSumQueryResponse queryDeviceReceivablesSum(ReceivablesDeviceQueryRequest request) {
        ReceivablesDeviceSumQueryResponse response = new ReceivablesDeviceSumQueryResponse();
        ReceivablesDeviceQueryDto queryDto = ReceivablesDeviceQueryDto.convertToDto(request);
        CreateStatementDto createStatementDto = BeanUtil.copyProperties(request, CreateStatementDto.class);

        List<CreateStatementDeviceDto> deviceDetails = createStatementDto.getDeviceDetails();
        List<DeviceRentalRecordPo> deviceRentalRecordParams = new ArrayList<>();
        deviceDetails.forEach(d -> {
            DeviceRentalRecordPo deviceRentalRecordPo = new DeviceRentalRecordPo();
            deviceRentalRecordPo.setId(d.getDeviceRentalRecordId());
            deviceRentalRecordPo.setDeviceNo(d.getDeviceNo());
            deviceRentalRecordPo.setContractNo(d.getContractNo());
            deviceRentalRecordParams.add(deviceRentalRecordPo);
        });
        List<DeviceRentalRecordPo> deviceRentalRecordPos = deviceRentalRecordMapper.selectDeviceByDeviceNoAndContractNo(deviceRentalRecordParams);

        LocalDateTime expenseCycleEndTime = createStatementDto.getStatementEndTime();

        for (CreateStatementDeviceDto d : deviceDetails) {
            ContractDeviceDetailDto detailDto = queryDto.getDeviceDetails().stream().filter(req -> d.getDeviceRentalRecordId().equals(req.getDeviceRentalRecordId()))
                    .findFirst().get();

            d.setSetPreStartTime(detailDto.getSetPreStartTime());
            d.setSetPreEndTime(detailDto.getSetPreEndTime());//设置设备纬度予定的账单开始时间和结束时间
            Optional<DeviceRentalRecordPo> deviceRentalRecordPoOptional = deviceRentalRecordPos.stream()
                    .filter(deviceRentalRecordPo -> d.getDeviceRentalRecordId().equals(deviceRentalRecordPo.getId())).findFirst();
            if (deviceRentalRecordPoOptional.isPresent()) {
                DeviceRentalRecordPo deviceRentalRecordPo = deviceRentalRecordPoOptional.get();
                d.setContractId(deviceRentalRecordPo.getContractId());
                d.setContractNo(deviceRentalRecordPo.getContractNo());
                d.setDeviceId(deviceRentalRecordPo.getDeviceId());
                d.setDeviceNo(deviceRentalRecordPo.getDeviceNo());
                d.setDeviceModel(deviceRentalRecordPo.getDeviceExtensionInfo().getDeviceModel());
                d.setRentalUnitPrice(deviceRentalRecordPo.getUseMonthlyRentalPrice() ? deviceRentalRecordPo.getMonthlyRentalPrice().divide(new BigDecimal(30), 2, RoundingMode.HALF_UP) : deviceRentalRecordPo.getDailyRentalPrice());
                d.setMonthlyRentalPrice(deviceRentalRecordPo.getMonthlyRentalPrice());
                d.setDailyRentalPrice(deviceRentalRecordPo.getDailyRentalPrice());
                if (deviceRentalRecordPo.getLastReconciliationTime().isEqual(deviceRentalRecordPo.getRentalStartTime())) {
                    d.setLastReconciliationTime(deviceRentalRecordPo.getLastReconciliationTime());
                } else {
                    d.setLastReconciliationTime(deviceRentalRecordPo.getLastReconciliationTime().plusSeconds(1));
                }
                d.setUseMonthlyRentalPrice(deviceRentalRecordPo.getUseMonthlyRentalPrice());
                d.setDeviceBeginRentTime(deviceRentalRecordPo.getRentalStartTime());
                d.setDeviceEndRentTime(deviceRentalRecordPo.getRentalEndTime());
                d.setExpenseCycleEndTime(deviceRentalRecordPo.getExpenseCycleEndTime());
                //对比expenseCycleEndTime和deviceRentalRecordPo.getExpenseCycleEndTime()，取最大值
                if (Objects.nonNull(d.getExpenseCycleEndTime()) && expenseCycleEndTime.isBefore(d.getExpenseCycleEndTime())) {
                    expenseCycleEndTime = d.getExpenseCycleEndTime();
                }
                d.setContractExtensionInfo(deviceRentalRecordPo.getContractExtensionInfo());
                d.setCustomerExtensionInfo(deviceRentalRecordPo.getCustomerExtensionInfo());
            }
        }
        createStatementDto.setExpenseCycleEndTime(expenseCycleEndTime);
        log.info("queryDeviceReceivablesSum createStatementDto:{}", JSON.toJSONString(createStatementDto));

        DefaultStatementHandler statementTypeHandle = StatementHandlerFactory.getHandlerByType(StatementTypeEnum.getEnumByValue(createStatementDto.getStatementType()));
        ReconciliationStatementDto reconciliationStatementDto = statementTypeHandle.doStatementHandler(createStatementDto);

        //往期金额
        BigDecimal lastTotalAmt;
        if (request.getStatementType().equals(StatementTypeEnum.TOTAL.getValue())) {
            lastTotalAmt = deviceRentalRecordPos.stream().map(DeviceRentalRecordPo::getHistoryReconciledAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        } else {
            lastTotalAmt = deviceRentalRecordPos.stream().map(DeviceRentalRecordPo::getTotalReconciledAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        //查询应收盘点数据
        Long contractId = deviceDetails.get(0).getContractId();

        List<ReceivableInventoryPo> receivableInventoryPoList = getReceivableInventoryPoList(List.of(contractId));
        //已回款金额
        BigDecimal totalReceivedAmount = receivableInventoryPoList.stream().map(ReceivableInventoryPo::getTotalReceivedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);

        //当期金额 = 租金 + 其他金额 - 运费减免 - 改装减免 + 应收调整 + 进退场费 + 改装费
        BigDecimal realTotalAmt = reconciliationStatementDto.getReconciliationTotalAmount();

        //组装数据
        response.setTotalRealAmount(realTotalAmt.stripTrailingZeros().toPlainString());
        response.setTotalLastAmount(lastTotalAmt.stripTrailingZeros().toPlainString());
        response.setTotalAmount(realTotalAmt.add(lastTotalAmt).stripTrailingZeros().toPlainString());
        response.setTotalReceivedAmount(totalReceivedAmount.stripTrailingZeros().toPlainString());
        response.setTotalArrearsAmount(realTotalAmt.add(lastTotalAmt).subtract(totalReceivedAmount).stripTrailingZeros().toPlainString());
        return response;
    }

    @Override
    public BasePageResponse<ReceivablesDevicePageQueryResponse> pageQueryReceivablesDevice(ReceivablesDeviceQueryRequest request) {
        ReceivablesDeviceQueryDto queryDto = ReceivablesDeviceQueryDto.convertToDto(request);

        //查询设备租赁记录
        Page<DeviceRentalRecordPo> poPage = deviceRentalRecordMapper.pageQuery(new Page<>(queryDto.getPageNum(), queryDto.getPageSize()),
                queryDto.getDeviceRentalRecordQueryPoList());

        if (CollUtil.isEmpty(poPage.getRecords())) {
            return new BasePageResponse<>();
        }

        Map<Long, ContractDeviceDetailDto> map = queryDto.getDeviceDetails().stream().collect(Collectors.toMap(ContractDeviceDetailDto::getDeviceRentalRecordId, Function.identity(), (k1, k2) -> k2));

        //过滤出不在账单开始-结束的账期内的记录
        List<DeviceRentalRecordPo> errorList = Lists.newArrayList();
        if (!queryDto.getStatementType().equals(StatementTypeEnum.TOTAL.getValue())) {
            poPage.getRecords().forEach(item -> {
                ContractDeviceDetailDto detailDto = map.get(item.getId());
                if (Objects.isNull(detailDto)) {
                    return;
                }
                if (!isLastReconciliationTimeBetween(item.getRentalStartTime(), item.getLastReconciliationTime(), detailDto.getSetPreStartTime(), detailDto.getSetPreEndTime())) {
                    errorList.add(item);
                }
            });

            if (CollUtil.isNotEmpty(errorList)) {
                StringBuffer errorDeviceStr = new StringBuffer();
                errorList.forEach(errorStatementDeviceDto -> {
                    errorDeviceStr.append("设备号:").append(errorStatementDeviceDto.getDeviceNo()).append("\n");
                });
                // 抛出异常
                throw new HMYRuntimeException(506300001, new Object[]{errorDeviceStr});
            }
        }

        List<DeviceDailyBillTimeQueryPo> queryPoList = Lists.newArrayList();

        //组装数据
        List<ReceivablesDevicePageQueryResponse> queryResponseList = Lists.newArrayList();
        List<DeviceRentalRecordPo> poPageRecordList = poPage.getRecords();

        /*查询之前已签期账单的金额(设备纬度)，作为跨账单但是没有过第一周期的未来账单费用计算扣除*/
        List<DeviceSignedRentalRo> deviceSignedRentalRos = reconciliationDeviceStatementMapper.selectSignedRentRecordsByDeviceRentalRecordIds(poPageRecordList.stream().map(DeviceRentalRecordPo::getId).collect(Collectors.toList()));

        poPageRecordList.forEach(item -> {
            //出参
            ReceivablesDevicePageQueryResponse queryResponse = BeanUtil.copyProperties(item, ReceivablesDevicePageQueryResponse.class);
            queryResponse.setDeviceRentalRecordId(item.getId());
            //计算设备当期起算时间 结束时间
            Pair<LocalDateTime, LocalDateTime> timePair = this.calcLateStatementTime(item, queryDto);
            queryResponse.setSetPreStartTime(timePair.getLeft());
            queryResponse.setSetPreEndTime(timePair.getRight());

            queryResponseList.add(queryResponse);

            //查询设备日账单流水-组装入参
            DeviceDailyBillTimeQueryPo queryPo = buildDeviceDailyBillTimeQueryPo(item, timePair.getLeft(), timePair.getRight());
            queryPoList.add(queryPo);
        });
        List<DeviceDailyBillPo> deviceDailyBillPoList = deviceDailyBillMapper.selectListByContractIdAndDeviceId(queryPoList);

        Map<String, List<DeviceDailyBillPo>> deviceBillMap = getGroupMapByContractIdAndDeviceId(deviceDailyBillPoList);
        Map<String, ReceivablesDevicePageQueryResponse> responseMap = getMapByContractIdAndDeviceId(queryResponseList);
        Map<String, DeviceRentalRecordPo> rentalMap = getRentalMapByContractIdAndDeviceId(poPageRecordList);

        //计算费用
        deviceBillMap.forEach((groupKey, billPoList) -> {
            //其他费用
            AtomicReference<BigDecimal> otherFee = new AtomicReference<>(BigDecimal.ZERO);
            //运费减免
            AtomicReference<BigDecimal> freightFee = new AtomicReference<>(BigDecimal.ZERO);
            //改装减免
            AtomicReference<BigDecimal> refitFee = new AtomicReference<>(BigDecimal.ZERO);
            //租金
            AtomicReference<BigDecimal> deviceFee = new AtomicReference<>(BigDecimal.ZERO);
            //报停费用
            AtomicReference<BigDecimal> pauseFee = new AtomicReference<>(BigDecimal.ZERO);
            //入场日非全日费用减免费用
            AtomicReference<BigDecimal> initialDayWaiverFee = new AtomicReference<>(BigDecimal.ZERO);
            //入场日非全日费用费用
            AtomicReference<BigDecimal> initialDayFee = new AtomicReference<>(BigDecimal.ZERO);

            ReceivablesDevicePageQueryResponse pageQueryResponse = responseMap.get(groupKey);
            DeviceRentalRecordPo rentalRecordPo = rentalMap.get(groupKey);

            calcFee(billPoList, otherFee, freightFee, refitFee, deviceFee, pauseFee, initialDayWaiverFee, initialDayFee);

            /*用于计算是否报停天数，涉及到是否跨周期的计算，即：*/
            //账单报停天数
            Integer statementPeriodPauseDays = billPoList.stream().filter(d -> d.getFee().equals(new BigDecimal(0)) && Boolean.TRUE.equals(d.getPauseFlag())).collect(Collectors.toList())
                    .size();//计算本期账单内报停的天数:并且为全天报停，不是全天报停的不算在内
            /*用于计算计租天数*/
            BigDecimal chargePauseDays = billPoList.stream().filter(d -> Boolean.TRUE.equals(d.getPauseFlag())).map(d -> BigDecimal.valueOf(1 - d.getChargeDays()))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);//计算本期账单内报停的天数:只要是报停，就计算，不管报停0.5还是0.1天
            /*查询是否存在未生效的报停单进行未来周期费用补足*/
            List<DeviceProfitLossDetailPo> devicePauseLossDetailRos = deviceProfitLossDetailMapper.selectDevicePauseLossDetailByDeviceIdAndCutoffDate(pageQueryResponse.getDeviceId(),
                    pageQueryResponse.getDeviceRentalRecordId(), pageQueryResponse.getSetPreEndTime().withHour(23).withMinute(59).withSecond(59));
            if (CollectionUtil.isNotEmpty(devicePauseLossDetailRos)) {
                //如果账期内存在未处理的报停数据，则将其转换为map
                //对deviceProfitLossDetailRos中的days进行求和,days为Double型
                BigDecimal futurePauseDays = new BigDecimal(devicePauseLossDetailRos.stream().map(DeviceProfitLossDetailPo::getDays).reduce(new Double(0), Double::sum));
                chargePauseDays = chargePauseDays.add(futurePauseDays);
            }

            //账期内总天数 = 当期结束时间 - 当期起算时间 + 1天
            BigDecimal statementTotalDays = BigDecimal.valueOf(iReconciliationStatementProviderService.calculateDaysBetween(pageQueryResponse.getSetPreStartTime(), pageQueryResponse.getSetPreEndTime()) + 1);

            double startDay = 1;
            double endDay = 1;
            if (pageQueryResponse.getSetPreStartTime().toLocalDate().isEqual(rentalRecordPo.getRentalStartTime().toLocalDate())) {
                startDay = FinanceCalculateUtil.getRentalStartDay(rentalRecordPo.getRentalStartTime());
            }
            if (Objects.nonNull(rentalRecordPo.getRentalEndTime()) && pageQueryResponse.getSetPreEndTime().toLocalDate().isEqual(rentalRecordPo.getRentalEndTime().toLocalDate())) {
                endDay = FinanceCalculateUtil.getRentalTerminationDay(pageQueryResponse.getSetPreEndTime());
            }

            BigDecimal chargeDays;
            if (Objects.nonNull(rentalRecordPo.getRentalEndTime())
                    && rentalRecordPo.getRentalStartTime().toLocalDate().isEqual(rentalRecordPo.getRentalEndTime().toLocalDate())) {
                chargeDays = statementTotalDays.subtract(chargePauseDays);//如果当天进场，当天退场，则算一天。

            } else {
                chargeDays = statementTotalDays.subtract(BigDecimal.valueOf(2)).add(BigDecimal.valueOf(startDay)).add(BigDecimal.valueOf(endDay)).subtract(chargePauseDays);
            }

            //计租天数
            pageQueryResponse.setRentalDays(chargeDays);
            pageQueryResponse.setTotalDays(statementTotalDays);

            //租金总计 = 租金 + 未来账期租金 - 入场日非全日费用减免费用 + 入场日非全日费用费用
            //step1:先计算本期账单费用(去除减免后的费用),用于计算未来账期
            deviceFee.getAndSet(deviceFee.get().subtract(initialDayWaiverFee.get()).add(initialDayFee.get()));

            //计算未来账期
            BigDecimal futureFee = futureFeeCalculateHandler(queryDto.getStatementType(), rentalRecordPo, pageQueryResponse.getSetPreEndTime(), deviceFee, billPoList, devicePauseLossDetailRos,
                    statementPeriodPauseDays, deviceSignedRentalRos);
            //step2:再加上未来账期
            deviceFee.getAndSet(deviceFee.get().add(futureFee));

            pageQueryResponse.setOtherFee(otherFee.get().stripTrailingZeros().toPlainString());
            pageQueryResponse.setFreightFee(freightFee.get().stripTrailingZeros().toPlainString());
            pageQueryResponse.setRefitFee(refitFee.get().stripTrailingZeros().toPlainString());
            pageQueryResponse.setDeviceFee(deviceFee.get().stripTrailingZeros().toPlainString());
            pageQueryResponse.setPauseFee(pauseFee.get().negate().stripTrailingZeros().toPlainString());
            pageQueryResponse.setDeviceStatementStatusDesc(Objects.nonNull(rentalRecordPo.getRentalEndTime()) ? "退租" : "在租");
        });

        //构造出参
        BasePageResponse<ReceivablesDevicePageQueryResponse> pageResponse = new BasePageResponse<>();
        pageResponse.setRecords(queryResponseList);
        pageResponse.setTotal(poPage.getTotal());
        pageResponse.setPages(poPage.getPages());
        pageResponse.setSize(poPage.getSize());
        pageResponse.setCurrent(poPage.getCurrent());

        return pageResponse;
    }

    /**
     * 计算计租天数
     *
     * @param rentalStartTime 起租时间
     * @param rentalEndTime   退租时间
     * @param startTime       当期开始时间
     * @param endTime         当期结束时间
     */
    private Integer calcRentalDays(LocalDateTime rentalStartTime, LocalDateTime rentalEndTime, LocalDateTime startTime, LocalDateTime endTime) {
        //退租时间为null,在租状态 或者 当期结束时间小于退租时间时
        Integer rentalDays = 0;
        if (Objects.isNull(rentalEndTime) || endTime.compareTo(rentalEndTime) < 0) {
            //当期起算时间小于起租时间时
            if (startTime.compareTo(rentalStartTime) < 0) {
                //计租天数 = 当期结束时间 - 起租时间 + 1天
                rentalDays = iReconciliationStatementProviderService.calculateDaysBetween(rentalStartTime, endTime);
                return rentalDays + 1;
            } else {
                // 计租天数 = 当期结束时间 - 起算时间 + 1天
                rentalDays = iReconciliationStatementProviderService.calculateDaysBetween(startTime, endTime);
                return rentalDays + 1;
            }
            //已退租
        } else {
            //当期结束时间大于等于退租时间时,结束时间取退租时间来计算
            if (endTime.compareTo(rentalEndTime) >= 0) {
                //当期起算时间小于起租时间时
                if (startTime.compareTo(rentalStartTime) < 0) {
                    // 计租天数 = 退租时间 - 起租时间 + 1天
                    rentalDays = iReconciliationStatementProviderService.calculateDaysBetween(rentalStartTime, rentalEndTime);
                    return rentalDays + 1;
                } else {
                    // 计租天数 = 退租时间 - 起算时间 + 1天
                    rentalDays = iReconciliationStatementProviderService.calculateDaysBetween(startTime, rentalEndTime);
                    return rentalDays + 1;
                }
            }
        }
        return rentalDays;
    }

    /**
     * 计算费用
     *
     * @param billPoList 设备日账单流水
     * @param otherFee   其他费用(索赔)
     * @param freightFee 运费减免
     * @param refitFee   改装减免
     * @param deviceFee  租金
     */
    private void calcFee(List<DeviceDailyBillPo> billPoList, AtomicReference<BigDecimal> otherFee, AtomicReference<BigDecimal> freightFee, AtomicReference<BigDecimal> refitFee,
                         AtomicReference<BigDecimal> deviceFee, AtomicReference<BigDecimal> pauseFee, AtomicReference<BigDecimal> initialDayWaiverFee, AtomicReference<BigDecimal> initialDayFee) {
        billPoList.forEach(item -> {
            //设备日账单流水对应的损益记录
            ObjectMapper mapper = new ObjectMapper();
            List<ProfitLossInfoJson> profitLossInfoList = mapper.convertValue(item.getProfitLossInfo(), new TypeReference<List<ProfitLossInfoJson>>() {
            });
            profitLossInfoList.forEach(i -> {
                //索赔费用
                if (Objects.equals(BusinessBillTypeEnum.CLAIMS.getValue(), i.getBusinessBillType()) && Objects.equals(FeeTypeEnum.CLAIMS_FEE.getValue(), i.getType())) {
                    otherFee.getAndSet(otherFee.get().add(i.getFee()));
                }

                //运费减免
                if (Objects.equals(FeeTypeEnum.SHIPPING_FEE_WAIVER.getValue(), i.getType())) {
                    freightFee.getAndSet(freightFee.get().add(i.getFee()));
                }

                //改装减免
                if (Objects.equals(FeeTypeEnum.MODIFICATION_FEE_WAIVER.getValue(), i.getType())) {
                    refitFee.getAndSet(refitFee.get().add(i.getFee()));
                }

                //报停费用
                if (Objects.equals(FeeTypeEnum.PAUSE_FEE.getValue(), i.getType()) || Objects.equals(FeeTypeEnum.SUPPLEMENTARY_PAUSE_FEE.getValue(), i.getType())) {
                    pauseFee.getAndSet(pauseFee.get().add(i.getFee()));
                }

                //入场日非全日费用减免费用
                if (Objects.equals(BusinessBillTypeEnum.ENTRY.getValue(), i.getBusinessBillType()) && Objects.equals(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER.getValue(), i.getType())) {
                    initialDayWaiverFee.getAndSet(initialDayWaiverFee.get().add(i.getFee()));
                }

                //入场日非全日费用
                if (Objects.equals(BusinessBillTypeEnum.ENTRY.getValue(), i.getBusinessBillType()) && Objects.equals(FeeTypeEnum.INITIAL_DAY_FEE.getValue(), i.getType())) {
                    initialDayFee.getAndSet(initialDayFee.get().add(i.getFee()));
                }
            });

            //租金
            deviceFee.getAndSet(deviceFee.get().add(item.getFee()));
        });
    }


    /**
     * 计算未来账期
     *
     * @param statementType        账期类型 StatementTypeEnum
     * @param deviceRentalRecordPo 设备租赁记录
     * @param currentRentalEndTime 本期设备结束时间
     * @param deviceFee            本期租金金额
     * @param billPoList           设备日费用账单流水
     * @return
     */
    private BigDecimal futureFeeCalculateHandler(String statementType, DeviceRentalRecordPo deviceRentalRecordPo, LocalDateTime currentRentalEndTime, AtomicReference<BigDecimal> deviceFee,
                                                 List<DeviceDailyBillPo> billPoList, List<DeviceProfitLossDetailPo> deviceProfitLossDetailRos, Integer statementPeriodPauseDays, List<DeviceSignedRentalRo> deviceSignedRentalRos) {
        //计算未来账期
        //取得deviceDailyBillList中feeIncurredTime最晚的日期
        LocalDateTime feeIncurredTime = billPoList.stream().map(DeviceDailyBillPo::getFeeIncurredTime).max(LocalDateTime::compareTo).get();
        Integer futureDays = iReconciliationStatementProviderService.calculateDaysBetween(feeIncurredTime, currentRentalEndTime);
        if (futureDays > 0) {
            /*查询是否存在未生效的报停单进行未来周期费用补足*/
            Map<LocalDate, Double> pauseLossMap = new HashMap<>();
            if (CollectionUtil.isNotEmpty(deviceProfitLossDetailRos)) {
                //如果账期内存在未处理的报停数据，则将其转换为map
                pauseLossMap = deviceProfitLossDetailRos.stream().collect(Collectors.toMap(l -> l.getFeeIncurredTime().toLocalDate(), DeviceProfitLossDetailPo::getDays));
            }
            //组装参数
            CreateStatementDeviceDto calculateStatementDeviceDto = new CreateStatementDeviceDto();
            calculateStatementDeviceDto.setDeviceBeginRentTime(deviceRentalRecordPo.getRentalStartTime());
            calculateStatementDeviceDto.setDailyRentalPrice(deviceRentalRecordPo.getDailyRentalPrice());
            calculateStatementDeviceDto.setMonthlyRentalPrice(deviceRentalRecordPo.getMonthlyRentalPrice());
            /*取入场第一天流水，设置为入场首日租金;*/
            List<DeviceDailyBillPo> deviceEarliestDailyBillRos = billPoList.stream().filter(item -> item.getType() == 1).collect(Collectors.toList());
            deviceEarliestDailyBillRos.stream().filter(d -> d.getDeviceRentalRecordId().equals(deviceRentalRecordPo.getId())).findFirst().ifPresent(d -> {
                calculateStatementDeviceDto.setInitialDayRentFee(d.getFee());
                calculateStatementDeviceDto.setInitialDay(d.getChargeDays());
            });
            deviceSignedRentalRos.stream().filter(d -> d.getDeviceRentalRecordId().equals(deviceRentalRecordPo.getId())).findFirst().ifPresent(d -> {
                calculateStatementDeviceDto.setSignedRentFee(d.getSignedRentPayable());
            });
            return iReconciliationStatementProviderService.futureFeeCalculateHandler(calculateStatementDeviceDto, deviceFee.get(), statementPeriodPauseDays, feeIncurredTime, currentRentalEndTime, pauseLossMap);
        }
        return BigDecimal.ZERO;
    }

    /**
     * 按照合同Id 设备id进行分组
     *
     * @param billPoList
     * @return
     */
    private Map<String, List<DeviceDailyBillPo>> getGroupMapByContractIdAndDeviceId(List<DeviceDailyBillPo> billPoList) {
        Function<DeviceDailyBillPo, String> compositeKey = dataRecord -> {
            return buildGroupKey(dataRecord.getContractId(), dataRecord.getDeviceId(), dataRecord.getDeviceRentalRecordId());
        };
        return billPoList.stream().collect(Collectors.groupingBy(compositeKey));
    }

    private Map<String, ReceivablesDevicePageQueryResponse> getMapByContractIdAndDeviceId(List<ReceivablesDevicePageQueryResponse> responseList) {
        Function<ReceivablesDevicePageQueryResponse, String> compositeKey = dataRecord -> {
            return buildGroupKey(dataRecord.getContractId(), dataRecord.getDeviceId(), dataRecord.getDeviceRentalRecordId());
        };
        return responseList.stream().collect(Collectors.toMap(compositeKey, Function.identity(), (k1, k2) -> k1));
    }

    private Map<String, DeviceRentalRecordPo> getRentalMapByContractIdAndDeviceId(List<DeviceRentalRecordPo> poPageRecordList) {
        Function<DeviceRentalRecordPo, String> compositeKey = dataRecord -> {
            return buildGroupKey(dataRecord.getContractId(), dataRecord.getDeviceId(), dataRecord.getId());
        };
        return poPageRecordList.stream().collect(Collectors.toMap(compositeKey, Function.identity(), (k1, k2) -> k1));
    }

    private String buildGroupKey(Long contractId, Long deviceId, Long deviceRentalRecordId) {
        return contractId.toString().concat("_").concat(deviceId.toString()).concat("_").concat(deviceRentalRecordId.toString());
    }

    /**
     * 计算设备的起算 结束时间,组装参数
     */
    private CalculateStatementDeviceDto buildCalculateStatementDeviceDto(DeviceRentalRecordPo rentalRecordPo, ReceivablesDeviceQueryDto queryDto) {

        CalculateStatementDeviceDto calculateStatementDeviceDto = new CalculateStatementDeviceDto();
        calculateStatementDeviceDto.setDeviceBeginRentTime(rentalRecordPo.getRentalStartTime());
        calculateStatementDeviceDto.setDeviceEndRentTime(rentalRecordPo.getRentalEndTime());
        calculateStatementDeviceDto.setLastReconciliationTime(rentalRecordPo.getLastReconciliationTime());
//        calculateStatementDeviceDto.setSetPreStartTime(queryDto.getStatementStartTime());
//        calculateStatementDeviceDto.setSetPreEndTime(queryDto.getStatementEndTime());

        return calculateStatementDeviceDto;
    }

    private Pair<LocalDateTime, LocalDateTime> calcLateStatementTime(DeviceRentalRecordPo rentalRecordPo, ReceivablesDeviceQueryDto queryDto) {
        //当期结束时间
        LocalDateTime endTime = null;
        StatementTypeEnum statementTypeEnum = StatementTypeEnum.getEnumByValue(queryDto.getStatementType());
        ContractDeviceDetailDto detailQueryDto = queryDto.getDeviceDetails().stream().filter(item -> item.getDeviceRentalRecordId().equals(rentalRecordPo.getId())).findFirst().get();
        switch (statementTypeEnum) {
            case MONTHLY:
                //月结
                endTime = monthStatementHandler.calculateDeviceEndTime(detailQueryDto.getSetPreEndTime(), rentalRecordPo.getRentalEndTime());
                return Pair.of(calcStartTime(rentalRecordPo.getRentalStartTime(), rentalRecordPo.getLastReconciliationTime()), endTime);
            case PERIODIC:
                //期结
                endTime = periodStatementHandler.calculateStatementEndTime(rentalRecordPo.getLastReconciliationTime(), rentalRecordPo.getRentalEndTime(), 30);
                //开始时间设置为上一次对账时间
                return Pair.of(calcStartTime(rentalRecordPo.getRentalStartTime(), rentalRecordPo.getLastReconciliationTime()), endTime);
            case TOTAL:
                //总账单 时间字段取外层statementEndTime字段 不取设备明细中的时间,兼容前端未保持一致
                //退租时间为null,取前端传值的结束时间
                if (Objects.isNull(rentalRecordPo.getRentalEndTime())) {
                    endTime = queryDto.getStatementEndTime().withHour(23).withMinute(59).withSecond(59);
                } else {
                    endTime = rentalRecordPo.getRentalEndTime().isBefore(queryDto.getStatementEndTime()) ? rentalRecordPo.getRentalEndTime() : queryDto.getStatementEndTime();
                }
                //开始时间设置为设备起租时间
                return Pair.of(rentalRecordPo.getRentalStartTime(), endTime);
            case FLEXIBLE:
                //灵活账期
                //结束时间
                if (Objects.nonNull(rentalRecordPo.getRentalEndTime())) {
                    endTime = rentalRecordPo.getRentalEndTime().isBefore(detailQueryDto.getSetPreEndTime()) ? rentalRecordPo.getRentalEndTime() : detailQueryDto.getSetPreEndTime();
                } else {
                    endTime = detailQueryDto.getSetPreEndTime().withHour(23).withMinute(59).withSecond(59).truncatedTo(java.time.temporal.ChronoUnit.SECONDS);
                }
                //开始时间设置为上一次对账时间
                return Pair.of(calcStartTime(rentalRecordPo.getRentalStartTime(), rentalRecordPo.getLastReconciliationTime()), endTime);
            default:
                throw new HMYRuntimeException("账期类型不存在");
        }
    }

    /**
     * 计算当期起算时间 起租时间不等于上次对账时间,则取上次对账时间+1s,反之等于则不+1s
     *
     * @param rentalStartTime        设备起租时间
     * @param lastReconciliationTime 设备上一次对账时间
     * @return
     */
    private LocalDateTime calcStartTime(LocalDateTime rentalStartTime, LocalDateTime lastReconciliationTime) {
        if (rentalStartTime.compareTo(lastReconciliationTime) == 0) {
            return lastReconciliationTime;
        }
        //上次对账时间+1秒
        return lastReconciliationTime.plusSeconds(1);
    }

    /**
     * 判断最近一次对账日期是否在本期账单时间范围内
     *
     * @param targetDateTime
     * @param startDateTime
     * @param endDateTime
     * @return
     */
    private boolean isLastReconciliationTimeBetween(LocalDateTime rentalStartTime, LocalDateTime targetDateTime, LocalDateTime startDateTime, LocalDateTime endDateTime) {
        //非首次,开始时间+1秒
        if (rentalStartTime.compareTo(targetDateTime) != 0) {
            //上次对账时间+1秒
            targetDateTime = targetDateTime.plusSeconds(1);
        }
        return !targetDateTime.isBefore(startDateTime) && !targetDateTime.isAfter(endDateTime);
    }

    private List<DeviceRentalRecordPo> getDeviceRentalRecordPoList(List<DeviceDailyBillPo> deviceDailyBillPoList) {
        //查询设备租赁记录
        List<Long> deviceRentalRecordIdList = deviceDailyBillPoList.stream().map(DeviceDailyBillPo::getDeviceRentalRecordId).distinct().collect(Collectors.toList());
        return deviceRentalRecordMapper.selectList(
                Wrappers.lambdaQuery(DeviceRentalRecordPo.class)
                        .in(DeviceRentalRecordPo::getId, deviceRentalRecordIdList)
        );
    }

    private List<ReceivableInventoryPo> getReceivableInventoryPoList(List<Long> contractIdList) {
        if (CollUtil.isEmpty(contractIdList)) {
            return Lists.newArrayList();
        }
        return receivableInventoryMapper.selectList(
                Wrappers.lambdaQuery(ReceivableInventoryPo.class)
                        .in(ReceivableInventoryPo::getContractId, contractIdList)
                        .eq(ReceivableInventoryPo::getIsDeleted, 0)
        );
    }

    private List<ProfitLossRecordPo> getProfitLossRecordPoList(List<Long> contractIdList) {
        if (CollUtil.isEmpty(contractIdList)) {
            return Lists.newArrayList();
        }
        return profitLossRecordMapper.selectList(
                Wrappers.lambdaQuery(ProfitLossRecordPo.class)
                        .in(ProfitLossRecordPo::getContractId, contractIdList)
        );
    }

    private DeviceDailyBillTimeQueryPo buildDeviceDailyBillTimeQueryPo(DeviceRentalRecordPo rentalRecordPo, LocalDateTime startTime, LocalDateTime endTime) {
        DeviceDailyBillTimeQueryPo queryPo = new DeviceDailyBillTimeQueryPo();
        queryPo.setDeviceRentalRecordId(rentalRecordPo.getId());
        queryPo.setDeviceId(rentalRecordPo.getDeviceId());
        queryPo.setContractId(rentalRecordPo.getContractId());
        queryPo.setFeeStatus(Boolean.TRUE);
        queryPo.setBillStartTime(startTime.withHour(00).withMinute(00).withSecond(00));

        /*如果设备已经退场，且账单需求拉取到结束时间，则使用账期结束时间来代替账单结束时间，以此拉到退场后一些损益的流水数据*/
        if (Objects.nonNull(rentalRecordPo.getRentalEndTime()) && endTime.toLocalDate().isEqual(rentalRecordPo.getRentalEndTime().toLocalDate())) {
            /*此处加一道栅栏，如果数据库没有账单结束时间，则使用退租时间*/
            if (Objects.nonNull(rentalRecordPo.getExpenseCycleEndTime())) {
                queryPo.setBillEndTime(rentalRecordPo.getExpenseCycleEndTime().withHour(23).withMinute(59).withSecond(59));
            } else {
                queryPo.setBillEndTime(rentalRecordPo.getRentalEndTime().withHour(23).withMinute(59).withSecond(59));
            }
        } else {
            queryPo.setBillEndTime(endTime.withHour(23).withMinute(59).withSecond(59));
        }
        return queryPo;
    }

}




